
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "cl_hud.lua" )
AddCSLuaFile( "shared.lua" )

include( "shared.lua" )
include( "levels.lua" )
include( "resources.lua" )

GM.TeamCurrentCheckpoint = {}
for i=1, 4 do GM.TeamCurrentCheckpoint[i] = 0 end
GM.TeamTotalProps = {}

local propPickupSound = Sound( "npc/scanner/scanner_nearmiss1.wav" )
local propDropSound = Sound( "npc/scanner/scanner_nearmiss2.wav" )
local checkpointSound = Sound( "ambient/levels/labs/machine_stop1.wav" )

local AFKThinkTimer = 0

local propThink = 0.5
local propLastThink = 0
local pointerLastThink = 0
local pointerThink = 0.20
local pointerCheckpointGap = 128
local totalPointerHeight = 416

function GM:KeyPress( ply, key )

	if ( key == IN_USE and ply:Alive() ) then

		local tr = ply:GetEyeTraceNoCursor()
		if ( ply.HoldingItem == nil and tr.Entity:IsValid() and tr.Entity:GetClass() == "ascension_prop" ) then

			if ( tr.Entity:GetOwner() == NULL and tr.Entity:GetPos():Distance( ply:GetPos() + Vector(0,0,64) ) < 100 ) then
			
				tr.Entity:EmitSound( propPickupSound )
				tr.Entity:SetOwner( ply )
				tr.Entity:SetCollisionGroup( COLLISION_GROUP_WEAPON )
				local phys = tr.Entity:GetPhysicsObject()
				if ( phys:IsValid() ) then 
					phys:Wake() 
					phys:EnableMotion( true )
				end
				tr.Entity:ActivateBlock()
				ply.HoldingItem = tr.Entity
				umsg.Start( "PlyIsHolding" )
					umsg.Entity( ply )
					umsg.Entity( ply.HoldingItem )
				umsg.End()
	
			end
	
		elseif ( ply.HoldingItem != nil ) then
		
			if ( ply.HoldingItem:IsValid() and ply.HoldingItem:GetOwner() == ply ) then
				
				local object = ply.HoldingItem
				timer.Simple( 0.5, function() // We need this delay to stop players boosting themselves really high when they drop blocks.
					if (object and object:IsValid()) then
						object:SetCollisionGroup( COLLISION_GROUP_NONE )
					end
				end )
				ply.HoldingItem:EmitSound( propDropSound )
				ply.HoldingItem:SetOwner( NULL )
				ply.HoldingItem:GetPhysicsObject():Wake()
				ply.HoldingItem = nil	
				umsg.Start( "PlyIsHolding" )
					umsg.Entity( ply )
					umsg.Entity( nil )
				umsg.End()
			
			end
		end
	end

end

function GM:NumPlyInCheckpoint( teamid, checkpointid )

	//TODO: find a better way to handle checkpoints?

	local str
	if ( teamid == TEAM_ORANGE ) then
		str = "orange_checkpoint"..checkpointid
	elseif ( teamid == TEAM_RED ) then
		str = "red_checkpoint"..checkpointid
	elseif ( teamid == TEAM_GREEN ) then
		str = "green_checkpoint"..checkpointid
	else
		str = "blue_checkpoint"..checkpointid
	end
	
	local num = 0
	for k, v in pairs( ents.FindByClass( str ) ) do
		num = v:GetNWInt("PlyInsideCheckpoint")
	end
	
	return num
	
end

function GM:OnRoundEnd( num )

	umsg.Start( "StartRestartTimer" )
		umsg.Long( GAMEMODE.RoundPostLength+CurTime() )
	umsg.End()

end

function GM:CheckRoundEnd()
 
 	//Get the total number of players alive
	local alivePlayers = 0
	for k,v in pairs( player.GetAll() ) do
		if ( v:Alive() ) then
			alivePlayers = alivePlayers + 1			 
		end
	end
 
	if ( GAMEMODE:InRound() ) then
 
		for teamid=1, 4 do
		
			//Get the number of players alive on specified team
			local numOfAlivePlayers = 0
			for k,v in pairs( team.GetPlayers( teamid ) ) do
				if ( v:Alive() ) then
					numOfAlivePlayers = numOfAlivePlayers + 1
				end
			end
			
			if ( alivePlayers > 1 and numOfAlivePlayers > 0 and numOfAlivePlayers == self:NumPlyInCheckpoint( teamid, 4 ) && self:NumPlyInCheckpoint( teamid, 3 ) == 0 ) then
				// We need to check if we're still in round just in case two teams finished at the exact same time. (So we can call "TeamReachedTopNotify")
				if (GAMEMODE:InRound()) then
					GAMEMODE:RoundEndWithResult( teamid, team.GetName(teamid) .. " wins!" )
					
					// Probably need to find a better way to do this, but we'll send the team colour as a vector
					local colour = team.GetColor( teamid )
					local effectdata = EffectData()
					effectdata:SetStart( Vector( colour.r, colour.g, colour.b ) )
					for delay=0, 7 do
						timer.Simple( delay, function()
							effectdata:SetOrigin( Vector( -1336 + (560*teamid),0,(1500) ) + (Vector(math.random(-4,4),0,math.random(-2,2)) * 20) )
							util.Effect( "effect_team_won", effectdata )
							
							util.Effect( "effect_team_won_bits", effectdata )
						end )
						
					end
				else
					self:TeamReachedTop( teamid )
				end
				self:TeamReachedCheckpoint( 4, teamid, true )
				self.TeamCurrentCheckpoint[teamid] = 4
			end
			
			for checkpoint=2, 3 do
			
				if ( numOfAlivePlayers > 0 and self:NumPlyInCheckpoint( teamid, checkpoint ) == numOfAlivePlayers ) then
					if ( self.TeamCurrentCheckpoint[teamid] < checkpoint && self:NumPlyInCheckpoint( teamid, self.TeamCurrentCheckpoint[teamid] ) == 0 ) then
						self.TeamCurrentCheckpoint[teamid] = checkpoint
						self:TeamReachedCheckpoint( checkpoint, teamid, false )
						
						umsg.Start( "CheckpointNotify" )
							umsg.Short( teamid )
							umsg.Short( checkpoint )
						umsg.End()
					end
				end
			
			end
		
		end
	
	else
	
		for teamid=1, 4 do
			local numOfAlivePlayers = 0
			for k,v in pairs( team.GetPlayers( teamid ) ) do
			 
				if ( v:Alive() ) then
						
					numOfAlivePlayers = numOfAlivePlayers + 1
			 
				end
			 
			end
		
			if ( alivePlayers > 1 and numOfAlivePlayers > 0 and numOfAlivePlayers == self:NumPlyInCheckpoint( teamid, 4 ) && self:NumPlyInCheckpoint( teamid, 3 ) == 0 ) then
				self:TeamReachedCheckpoint( 4, teamid, true )
				self:TeamReachedTop( teamid )
			end
		end
	
	end
 
end

function GM:OnRoundResult( result, resulttext )

	local num = 0
	for k, v in pairs( player.GetAll() ) do
		if ( v:Alive() ) then
			num = num + 1
		end
	end
	
	// We only want to add score when there's one or more players playing, so that a team doesn't get a score when there are no other players playing.
	if ( num > 1 ) then
		team.AddScore( result, 1 ) 
	end

end

function GM:TeamReachedTop( teamid )

	local position = 1
	for id=1, 4 do
		if ( self.TeamCurrentCheckpoint[id] == 4 and id != teamid ) then
			position = position + 1
		end
	end

	umsg.Start( "TeamReachedTopNotify" )
		umsg.Short( teamid )
		umsg.Short( position )
	umsg.End()

end

function IntializeAscensionEntities()

	for k, v in pairs( ents.FindByClass("func_wall") ) do
	
		local kvalue = v:GetKeyValues().target
		if ( kvalue != "" ) then
			GAMEMODE.MapProps[kvalue] = v
		end
		
	end
	
	for k, v in pairs( ents.FindByClass("func_illusionary") ) do
	
		local kvalue = v:GetKeyValues().target
		if ( kvalue != "" ) then
			GAMEMODE.MapIllusionaries[kvalue] = v
		end
		
	end
	
	// For appearance sake, we'll create some platforms and spawners to make it look pretty until the first player starts a game.
	GAMEMODE:ObstacleSetup()
	GAMEMODE:SpawnerSetup()

end
hook.Add( "InitPostEntity", "IntializeAscensionEntities", IntializeAscensionEntities )

function PlayerInitSpawn( ply )

	timer.Simple( 1, function()	
		umsg.Start( "CPlayerInitialSpawn_umsg" )
		umsg.End()
	end )
	
end
hook.Add( "PlayerInitialSpawn", "PlayerInitSpawn_umsg", PlayerInitSpawn ) 

function GM:TeamReachedCheckpoint( checkpoint, teamid, finished )
	
	// Find a better way to emit the gamemode related sounds?
	for k, v in pairs( player.GetAll() ) do
		v:EmitSound(checkpointSound)
		break
	end

	local teamcol = self:GetTeamColour( teamid )
	for hole=1, 3 do
		self:ChangeHoleVisibility( false, teamcol, checkpoint-1, hole, teamid )
	end
	
	umsg.Start( "UpdatePushEmitter" )
		umsg.Short( teamid )
		umsg.Short( checkpoint-1 )
		umsg.Bool( false )
		umsg.Vector( Vector(0,0,0) )
	umsg.End()
	
	if (!finished) then
		for k, ent in pairs( ents.FindByClass( "prop_spawner" ) ) do
			if ( ent:GetTeam() == teamid ) then
				ent:SetPos( ent:GetPos() + Vector(0,0,416) )
				ent:ResetLastProp()
			end
		end

		for k, ent in pairs( ents.FindByClass( "ascension_prop" ) ) do
			if ( ent:GetTeam() == teamid ) then
				local effectdata = EffectData()
					effectdata:SetOrigin( ent:GetPos() )
				util.Effect( "effect_prop_remove", effectdata )
				ent:Remove()
			end
		end
		
		for k, ply in pairs( team.GetPlayers( teamid ) ) do
		
			ply.HoldingItem = nil	
			umsg.Start( "PlyIsHolding" )
				umsg.Entity( ply )
				umsg.Entity( nil )
			umsg.End()
		
		end
		
		self.TeamTotalProps[teamid] = 0
	end
	
end

function GM:ObstacleSetup()
	
	for k, v in pairs( ents.FindByClass("trigger_push") ) do
		
		local kvalue = v:GetKeyValues().target
		if ( kvalue != "" ) then
			GAMEMODE.MapPushes[kvalue] = v
		end
		
	end
	
	local teamcol = 0
	
	for layer=0, 2 do
			
		local set = nil
			
		for i=1, 100 do
			set = math.random( 1, #self.LevelSets.holes )
			local used = false
			for i=0, 2 do
				if (set == self.LastSelected[i]) then
					used = true
				end
			end
			
			if (!used) then
				self.LastSelected[layer] = set
				break
			end
		end
		
		self.SelectedLevelHoles = self.LevelSets.holes[set]
		self.SelectedLevelProps	= self.LevelSets.props[set]
	
		for i=1, 4 do
		
			teamcol = self:GetTeamColour( i )
			
			for hv, h in SortedPairs( self.SelectedLevelHoles ) do
				if (h == 1) then
					self:ChangeHoleVisibility( false, teamcol, layer+1, hv, i )
				else
					self:ChangeHoleVisibility( true, teamcol, layer+1, hv, i )
				end
			end
			
			for k, prop in pairs( self.SelectedLevelProps ) do
				if (prop != nil && type(prop) == "Vector") then
					
					local rotation = nil
					if (self.SelectedLevelProps[k.."_rotation"]) then 
						rotation = self.SelectedLevelProps[k.."_rotation"]
					end
					
					self:CreateAscensionPlatform( prop + Vector( (-1656) + (560*i),0,(416*layer) ), i, rotation )
					
				end
			end
			
		end
		
	end
	

end

function GM:ChangeHoleVisibility( invisible, teamcol, checkpoint, hole, teamid )

	local prop = self.MapProps[tostring(teamcol.."_checkpoint"..checkpoint.."_hole"..hole)]
	prop:SetNotSolid( invisible )
	prop:SetNoDraw( invisible )
	
	local propIllusionary = self.MapIllusionaries[tostring(teamcol.."_checkpoint"..checkpoint.."_hole"..hole)]
	propIllusionary:SetNoDraw( invisible )
	
	local effect = self.MapIllusionaries[tostring(teamcol.."_checkpoint"..checkpoint.."_effect"..hole)]
	local push = self.MapPushes[tostring(teamcol.."_checkpoint"..checkpoint.."_push"..hole)]
	if ( effect:IsValid() ) then
		if (invisible) then
			local mins, maxs = prop:WorldSpaceAABB();  
			local pos = ( mins + maxs ) * 0.5;
			self:CreateAscensionPlatform( pos - Vector(0,0,124), teamid )

			effect:SetNoDraw( false )
			push:Fire( "enable", "", 0 )
			
			umsg.Start( "UpdatePushEmitter" )
				umsg.Short( teamid )
				umsg.Short( checkpoint )
				umsg.Bool( true )
				umsg.Vector( pos - Vector(0,0,110) )
			umsg.End()
		else
			effect:SetNoDraw( true )
			push:Fire( "disable", "", 0 )
		end
	end
	
end

function GM:CreateAscensionPlatform( pos, teamid, rotation )

	local ent = ents.Create( "prop_physics" )
	ent:SetModel( "models/ascension/objects/platform_01.mdl" )
	ent:SetPos( pos )
	if (rotation) then 
		ent:SetAngles( Angle(0,90,rotation) )
	else
		ent:SetAngles( Angle(0,90,0) )
	end
	ent:SetSkin( self:GetPlatformColour(teamid) )
	ent:Spawn()
			
	ent:GetPhysicsObject():EnableMotion( false )

end

function GM:GetPlatformColour( teamid )
	
	if (teamid == TEAM_ORANGE) then
		return 3
	elseif (teamid == TEAM_BLUE) then
		return 4
	elseif (teamid == TEAM_RED) then
		return 1
	elseif (teamid == TEAM_GREEN) then
		return 2
	end

end

function GM:GetTeamColour( teamid )

	if (teamid == TEAM_ORANGE) then
		return "orange"
	elseif (teamid == TEAM_GREEN) then		
		return "green"
	elseif (teamid == TEAM_RED) then
		return "red"
	elseif (teamid == TEAM_BLUE) then
		return "blue"
	end

end

function GM:SpawnerSetup()

	for i=1, 4 do
		// Reset teams prop count
		self.TeamTotalProps[i] = 0
	end

	for p=0, 1 do
		for i=1, 4 do
		
			local spawner = ents.Create( "prop_spawner" )
			spawner:SetPos( Vector( (-1592) + (384*p) + (560*i),0,0 ) )
			spawner:SetTeam( i )
			spawner:Spawn()
		
		end
	end

end

/* // Not sure if I need this function still, so i'm keeping it here just in case I do later on.
function GM:OnRoundStart( num )

	UTIL_UnFreezeAllPlayers() 

end
*/

function GM:OnPreRoundStart( num )

	game.CleanUpMap()  

	self:ObstacleSetup()
	self:SpawnerSetup()
	
	for teamid=1, 4 do
		self.TeamCurrentCheckpoint[teamid] = 1
	end	
	
	UTIL_StripAllPlayers()
	UTIL_SpawnAllPlayers()
	UTIL_FreezeAllPlayers()
	
	for k, ply in pairs( player.GetAll() ) do
		if ( ply and ply:IsValid() ) then
			ply.HoldingItem = nil	
			umsg.Start( "PlyIsHolding" )
				umsg.Entity( ply )
				umsg.Entity( nil )
			umsg.End()
			
			umsg.Start( "startGoMessage" )
			umsg.End()
		end
	end

end

function PointerThink()

	if ( pointerLastThink < CurTime() ) then
	
		local orangePos = 0
		local greenPos = 0
		local redPos = 0
		local bluePos = 0
	
		for teamID=1, 4 do
			local total = 0
			local totalValue = 0
		
			for k, ply in pairs( team.GetPlayers( teamID ) ) do
				if ( ply:Alive() ) then
					total = total + 1
					
					local value =  ply:GetPos().z + totalPointerHeight - (GAMEMODE.TeamCurrentCheckpoint[teamID]*totalPointerHeight)
					if (value > totalPointerHeight) then
						totalValue = totalValue + totalPointerHeight
					else
						totalValue = totalValue + value
					end
				end
			end
			
			if (totalValue > 0) then
			
				local checkpointNum = GAMEMODE.TeamCurrentCheckpoint[teamID]-1
					
				if ( checkpointNum < 3 ) then
					totalValue = (((totalValue/total) / totalPointerHeight) * pointerCheckpointGap) + (pointerCheckpointGap * checkpointNum)
				else
					totalValue = 384
				end
			
				if ( teamID == TEAM_ORANGE ) then
					orangePos = totalValue
				elseif ( teamID == TEAM_GREEN ) then
					greenPos = totalValue
				elseif ( teamID == TEAM_RED ) then
					redPos = totalValue
				elseif ( teamID == TEAM_BLUE ) then
					bluePos = totalValue
				end
			end
		end
	
		umsg.Start( "UpdatePointers" )
			umsg.Long( orangePos )
			umsg.Long( greenPos )
			umsg.Long( redPos )
			umsg.Long( bluePos )
		umsg.End()
	
		pointerLastThink = pointerThink + CurTime()
	
	end

end
hook.Add( "Think", "PointerThink", PointerThink )

function PropThink()

	for k, ply in pairs( player.GetAll() ) do
		if ( ply:Alive() ) then
			if ( ply:IsOnGround() /*and ply.SittingOnProp == nil */) then
				local ent = ply:GetGroundEntity()
				if ( ent:GetClass() == "ascension_prop" ) then
					if ( ply.SittingOnProp == nil ) then
						ply.SittingOnProp = ent
						local phys = ent:GetPhysicsObject()
						if ( phys:IsValid() ) then
							phys:EnableMotion( false )
						end
					end
				end
			elseif ( ply.SittingOnProp and ply.SittingOnProp:IsValid() ) then
				local phys = ply.SittingOnProp:GetPhysicsObject()
				if ( phys:IsValid() ) then
					phys:EnableMotion( true )
					ply.SittingOnProp = nil
				end
			
			end
		end
	end

end
hook.Add( "Think", "PropThink", PropThink )


function AFKThink()

	if ( AFKThinkTimer < CurTime() ) then
		for k, ply in pairs(player.GetAll()) do
			if ( ply:Team() != TEAM_SPECTATOR and ply:Alive() ) then
				if ( not ply.LastPos or not ply.LastMoved  ) then
					ply.LastPos = ply:GetPos()
					ply.LastMoved = CurTime()
				end
				
				if (CurTime()-ply.LastMoved > 45 and CurTime()-ply.LastMoved < 46 ) then
					ply:PrintMessage( HUD_PRINTTALK, "You will be moved to spectators unless you move within the next 15 seconds!" )
				end

				if ( ply.LastPos != ply:GetPos() ) then
					ply.LastMoved = CurTime()
					ply.LastPos = ply:GetPos()
				elseif (CurTime()-ply.LastMoved > 60) then
					ply:KillSilent()
					ply:SetTeam( TEAM_SPECTATOR )
					ply:PrintMessage( HUD_PRINTTALK, "You've been moved to spectators due to being idle." )
					ply.LastMoved = CurTime()
				end
				
			end
		end
		
		AFKThinkTimer = CurTime() + 1
	end

end
hook.Add( "Think", "AFKThink", AFKThink )


function GM:OnPlayerChangedTeam( ply, oldteam, newteam )

    if ( newteam == TEAM_SPECTATOR ) then

        // If we changed to spectator mode, respawn where we are
        local Pos = ply:EyePos()
        ply:Spawn()
        ply:SetPos( Pos )
	
	elseif ( oldteam == TEAM_SPECTATOR or oldteam == TEAM_UNASSIGNED ) then

		local plyAlive = 0
		for k, v in pairs( player.GetAll() ) do
			if ( v:Alive() ) then
				plyAlive = plyAlive + 1
			end
		end
		
		if ( plyAlive == 1 ) then
			if (GAMEMODE:InRound()) then
				GAMEMODE:RoundEndWithResult( 1001, "Draw - Nobody Wins" )
			end
		end
		//Check to see if there's more than one player alive before we spawn, otherwise we'll just wait for the new round to automatically start.
		//if ( plyAlive > 1 ) then
			ply:Spawn()
			
			local checkpoint = self.TeamCurrentCheckpoint[newteam]-1
			if ( checkpoint > 0 ) then
				ply:SetPos( ply:GetPos() + Vector(0,0,(totalPointerHeight*checkpoint)) )
			end
			
		//end

    else
		
    end

    PrintMessage( HUD_PRINTTALK, Format( "%s joined '%s'", ply:Nick(), team.GetName( newteam ) ) )

end

function GM:PlayerUse()
	
	//Return false so player's can't 'use'. (This is to remove the annoying use sound when you don't actually 'use' an entity.)
	return false
	
end

function GM:ShowSpare1( ply )

	if (ply.Overview) then
		ply.Overview = false
	else
		ply.Overview = true
	end
	
	umsg.Start( "ChangeOverview", ply )
		umsg.Bool( ply.Overview )
	umsg.End()
	

end